import argparse
import os
import json
import logging
import re
import sys
import time
import copy
from functools import partial
from concurrent.futures import ThreadPoolExecutor
from GnToolBox.gn_common_tool import GnCommonTool


class GnInfoCollector:

    @classmethod
    def __find_part_subsystem_in_bundle(cls, gnpath: str, stop_tail: str = "home") -> tuple:
        """
        根据BUILD.gn的全路径，一层层往上面查找bundle.json文件，
        并从bundle.json中查找part_name和subsystem
        """
        filename = "bundle.json"
        part_name = None
        subsystem_name = None
        if stop_tail not in gnpath:
            logging.error("{} not in {}".format(stop_tail, gnpath))
            return part_name, subsystem_name
        if os.path.isfile(gnpath):
            gnpath = os.path.split(gnpath)[0]
        while not gnpath.endswith(stop_tail):
            bundle_path = os.path.join(gnpath, filename)
            if not os.path.isfile(bundle_path):  # 如果该文件不在该目录下
                gnpath = os.path.split(gnpath)[0]
                continue
            with open(bundle_path, 'r', encoding='utf-8') as f:
                content = json.load(f)
                try:
                    part_name = content["component"]["name"]
                    subsystem_name = content["component"]["subsystem"]
                except KeyError:
                    logging.warning(
                        "not found component/name or component/subsystem in bundle.json")
                finally:
                    break
        part_name = None if (part_name is not None and len(
            part_name) == 0) else part_name
        subsystem_name = None if (subsystem_name is not None and len(
            subsystem_name) == 0) else subsystem_name
        return part_name, subsystem_name

    @classmethod
    def __find_part_subsystem(cls, gn_file: str, project_path: str) -> tuple:
        """
        查找gn_file对应的part_name和subsystem
        如果在gn中找不到，就到bundle.json中去找
        """
        part_name = None
        subsystem_name = None
        part_var_flag = False  # 标识这个变量从gn中取出的原始值是不是变量
        subsystem_var_flag = False
        var_list = list()
        part_name_pattern = r"part_name *=\s*\S*"
        subsystem_pattern = r"subsystem_name *=\s*\S*"
        meta_grep_pattern = "grep -E '{}' {} | head -n 1"
        part_cmd = meta_grep_pattern.format(part_name_pattern, gn_file)
        subsystem_cmd = meta_grep_pattern.format(subsystem_pattern, gn_file)
        part = os.popen(part_cmd).read().strip()
        if len(part) != 0:
            part = part.split('=')[-1].strip()
            if GnCommonTool.is_gn_variable(part):
                part_var_flag = True
                var_list.append(part)
            else:
                part_name = part.strip('"')
                if len(part_name) == 0:
                    part_name = None
        subsystem = os.popen(subsystem_cmd).read().strip()
        if len(subsystem) != 0:  # 这里是只是看有没有grep到关键字
            subsystem = subsystem.split('=')[-1].strip()
            if GnCommonTool.is_gn_variable(subsystem):
                subsystem_var_flag = True
                var_list.append(subsystem)
            else:
                subsystem_name = subsystem.strip('"')
                if len(subsystem_name) == 0:
                    subsystem_name = None
        if part_var_flag and subsystem_var_flag:
            part_name, subsystem_name = GnCommonTool.find_variables_in_gn(
                tuple(var_list), gn_file, project_path)
        elif part_var_flag:
            t = GnCommonTool.find_variables_in_gn(
                tuple(var_list), gn_file, project_path)[0]
            part_name = t if t is not None and len(t) != 0 else part_name
        elif subsystem_var_flag:
            t = GnCommonTool.find_variables_in_gn(
                tuple(var_list), gn_file, project_path)[0]
            subsystem_name = t if t is not None and len(
                t) != 0 else subsystem_name
        if part_name is not None and subsystem_name is not None:
            return part_name, subsystem_name
        # 如果有一个没有找到，就要一层层去找bundle.json文件
        t_part_name, t_subsystem_name = cls.__find_part_subsystem_in_bundle(
            gn_file, stop_tail=project_path)
        if t_part_name is not None:
            part_name = t_part_name
        if t_subsystem_name is not None:
            subsystem_name = t_subsystem_name
        return part_name, subsystem_name

    __unit_template = {"target_name": None,  # ohos_shared_library("xxx")中的xxx
                       "type": None,
                       "source_name": None,  # 如果是so或exe，则这个就是target_name，否则就是source_name字段的内容
                       "part_name": None,
                       "subsystem_name": None,
                       "line_no": None,  # 参考值，etc为None
                       "install_dir": None,  # so或exe的值默认为None，主要是给etc用
                       "gn_path": None}

    @classmethod
    def __put(cls, target_type: str, unit: dict):
        cls.__gn_info_dict.get(target_type).append(unit)

    @classmethod
    def __target_processor(cls, target_type: str, grep_line: str, project_path: str):
        """
        处理target资源
        """
        info_dict = copy.deepcopy(cls.__unit_template)
        gn_path, line_no = grep_line.split(":")[:2]
        target_pattern = re.compile(r'(\".*\")')
        target_name = re.search(target_pattern, grep_line).group(1)
        if GnCommonTool.is_gn_variable(target_name):
            target_name = GnCommonTool.find_variables_in_gn(
                (target_name,), gn_path, stop_tail=project_path)
        else:
            target_name = target_name.strip('"')
        part_name, subsystem_name = cls.__find_part_subsystem(
            gn_path, project_path)
        info_dict["target_name"] = target_name
        info_dict["type"] = target_type
        info_dict["source_name"] = target_name
        info_dict["gn_path"] = gn_path
        info_dict["line_no"] = line_no
        info_dict["part_name"] = part_name
        info_dict["subsystem_name"] = subsystem_name
        cls.__put(target_type, info_dict)

    # 用来记录处理过的gn文件，避免重复工作
    __etc_mem = set()

    @classmethod
    def __etc_processor(cls, target_type: str, grep_line: str, project_path: str):
        """
        处理etc资源
        target_type: eg: ohos_prebuilt_etc
        """
        gn_info_template = copy.deepcopy(cls.__unit_template)
        splited = grep_line.split(":")
        gn_path = splited[0]
        if gn_path in cls.__etc_mem:
            return None
        part_name, subsystem_name = cls.__find_part_subsystem(
            gn_path, project_path)
        gn_info_template["part_name"] = part_name
        gn_info_template["subsystem_name"] = subsystem_name
        gn_info_template["type"] = target_type
        gn_info_template["gn_path"] = gn_path
        etc_pattern = re.compile(r"{}\(\"(.*)\"\)".format(target_type))
        source_pattern = re.compile(r" *source *=\s*(.*)")
        module_install_root = "system"
        relative_install_root = "system/etc"
        module_install_dir_pattern = re.compile(
            r"\n *module_install_dir *= *(.*)")
        relative_install_dir_pattern = re.compile(
            r"\n *relative_install_dir *= *(.*)")
        with open(gn_path, 'r', encoding='utf-8') as f:
            content = f.read()
            iter = GnCommonTool.find_paragraph_iter(
                start_pattern=target_type, content=content)
            for p in iter:
                info = copy.deepcopy(gn_info_template)
                paragraph = p.group()
                etc = re.search(etc_pattern, paragraph)
                if etc is not None:
                    info["target_name"] = etc.group(1)
                source_base = re.search(source_pattern, paragraph)
                if source_base is not None:
                    info["source_name"] = source_base.group(
                        1).strip('"').split("/")[-1]
                module_install_dir = re.search(
                    module_install_dir_pattern, paragraph)
                if module_install_dir is not None:
                    info["install_dir"] = os.path.join(
                        module_install_root, module_install_dir.group(1).strip('"'))
                else:
                    relative_install_dir = re.search(
                        relative_install_dir_pattern, paragraph)
                    if relative_install_dir is not None:
                        info["install_dir"] = os.path.join(
                            relative_install_root, relative_install_dir.group(1).strip('"'))
                    else:
                        info["install_dir"] = relative_install_root
                cls.__put(target_type, info)
        cls.__etc_mem.add(gn_path)

    __profile_mem = set()

    @classmethod
    def __profile_processor(cls, target_type: str, grep_line: str, project_path: str):
        """
        处理profile资源
        target_type: eg: ohos_sa_profile
        """
        gn_info_template = copy.deepcopy(cls.__unit_template)
        splited = grep_line.split(":")
        gn_path = splited[0]
        if gn_path in cls.__profile_mem:
            return None
        part_name, subsystem_name = cls.__find_part_subsystem(
            gn_path, project_path)
        gn_info_template["part_name"] = part_name
        gn_info_template["subsystem_name"] = subsystem_name
        gn_info_template["type"] = target_type
        gn_info_template["gn_path"] = gn_path
        profile_pattern = re.compile(r"{}\(\"(.*)\"\)".format(target_type))
        sources_pattern = re.compile(r" *sources *=\s*\[([\s|\S]*)\]")
        module_install_root = "system/profile"
        relative_install_root = "system/profile"
        module_install_dir_pattern = re.compile(
            r"\n *module_install_dir *= *(.*)")
        relative_install_dir_pattern = re.compile(
            r"\n *relative_install_dir *= *(.*)")
        with open(gn_path, 'r', encoding='utf-8') as f:
            content = f.read()
            iter = GnCommonTool.find_paragraph_iter(
                start_pattern=target_type, content=content)
            for p in iter:
                info = copy.deepcopy(gn_info_template)
                paragraph = p.group()
                profile = re.search(profile_pattern, paragraph)
                if profile is not None:
                    info["target_name"] = profile.group(1)
                sources_base = re.search(sources_pattern, paragraph)
                if sources_base is None:    # sources base是多行
                    continue
                sources_list = list()
                for s in sources_base.group(1).split("\n"):
                    s = s.strip().rstrip(",").strip('"')
                    if len(s) != 0:
                        sources_list.append(s)
                for src in sources_list:
                    source_info = copy.deepcopy(info)
                    source_info["source_name"] = src.split("/")[-1]
                    module_install_dir = re.search(
                        module_install_dir_pattern, paragraph)
                    if module_install_dir is not None:
                        source_info["install_dir"] = os.path.join(
                            module_install_root, module_install_dir.group(1).strip('"'))
                    else:
                        relative_install_dir = re.search(
                            relative_install_dir_pattern, paragraph)
                        if relative_install_dir is not None:
                            source_info["install_dir"] = os.path.join(
                                relative_install_root, relative_install_dir.group(1).strip('"'))
                        else:
                            source_info["install_dir"] = module_install_root
                    cls.__put(target_type, source_info)
        cls.__profile_mem.add(gn_path)
    # 存储结果的字典
    __gn_info_dict = {
        "ohos_shared_library": list(),
        "shared_library": list(),
        "ohos_executable": list(),
        "executable": list(),
        "ohos_prebuilt_etc": list(),
        "ohos_sa_profile": list(),
    }

    # __gn_info_dict = {
    #     "ohos_shared_library": dict(),
    #     "shared_library": dict(),
    #     "ohos_executable": dict(),
    #     "executable": dict(),
    #     "ohos_prebuilt_etc": dict()
    # }
    @classmethod
    def collect_gn_info(cls, project_path: str, update=True):
        """
        在BUILD.gn中循环grep几种pattern
        对每一条找到的进行详细处理：
        包括查找target或资源名称、查找part_name与subsystem、分析出具体的gnfile以及用pattern定位到的行号
        """
        grep_struct = (
            ("ohos_shared_library", r"^( *)ohos_shared_library\(\".*\"\)",
             cls.__target_processor),
            ("shared_library", r"^( *)shared_library\(\".*\"\)", cls.__target_processor),
            ("ohos_executable", r"^( *)ohos_executable\(\".*\"\)",
             cls.__target_processor),
            ("executable", r"^( *)executable\(\".*\"\)", cls.__target_processor),
            ("ohos_prebuilt_etc", r"^( *)ohos_prebuilt_etc\(\".*\"\)", cls.__etc_processor),
            ("ohos_sa_profile", r"^( *)ohos_sa_profile\(\".*\"\)",
             cls.__profile_processor)
        )

        black_keywords = ("test", "import\(")  # 用于过滤grep出的文件
        black_dirs = (".repo", ".ccache", "doc", "test",
                      "build", "out")  # 给grep gn --exclude用的
        json_file = "gn_info.json"
        if update is False:
            if os.path.isfile(json_file):
                content = dict()
                with open(json_file, 'r', encoding='utf-8') as f:
                    content = f.read()
                return json.loads(content)
            else:
                logging.error(
                    "[collect_gn_info] param update is False, but file is not exist".format(json_file))
                return
        process = partial(cls.__process_template, project_path=project_path, black_keywords=black_keywords,
                          black_dirs=black_dirs)
        with ThreadPoolExecutor(max_workers=len(grep_struct) + 1) as pool:
            pool.map(process, grep_struct)
            # t0 = pool.submit(process, grep_struct[0])
            # t1 = pool.submit(process, grep_struct[1])
            # t2 = pool.submit(process, grep_struct[2])
            # t3 = pool.submit(process, grep_struct[3])
            # t4 = pool.submit(process, grep_struct[4])
            # t5 = pool.submit(process, grep_struct[5])
            # t0.result()
            # t1.result()
            # t2.result()
            # t3.result()
            # t4.result()
            # t5.result()
        with open(json_file, 'w', encoding='utf-8') as f:
            f.write(json.dumps(cls.__gn_info_dict, indent=4))
        return cls.__gn_info_dict

    @classmethod
    def __process_template(cls, ele_tuple: tuple, project_path: str, black_keywords: tuple, black_dirs: tuple):
        """
        处理各种template
        """
        print("processing: {}".format(ele_tuple[0]))
        output = GnCommonTool.grep(ele_tuple[1],
                                   grep_path=project_path,
                                   exclude=black_dirs,
                                   black_keyword=black_keywords)

        for line in output:
            ele_tuple[2](target_type=ele_tuple[0], grep_line=line.strip(),
                         project_path=project_path)


def test():
    parser = argparse.ArgumentParser()
    parser.add_argument("-p", "--project_path", default="/home/aodongbiao/rkmini", type=str,
                        help="root path of openharmony")
    args = parser.parse_args()
    project_path = os.path.abspath(args.project_path)
    logging.getLogger().setLevel(logging.ERROR)
    if sys.platform != "linux":
        logging.error("script only support linux")
        sys.exit(-1)
    start = time.time()
    GnInfoCollector.collect_gn_info(project_path)
    print("total time: {}".format(time.time() - start))


if __name__ == '__main__':
    test()
